package com.zhss.demo.concurrent;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * @author xuhua
 * @create 2021-12-19 18:09
 * @description 分布式锁高并发优化实战Demo
 */
public class RedisLockOptimizeDemo {

    public static void main(String[] args) {
        // 商品SKUid
        Long goodsSkuId = 1L;
        // 购买数量
        Long purchaseCount = 50L;
        // 库存段
        int stockSegmentSeq = new Random().nextInt(10) + 1;

        // 获取商品库存
        InventoryDAO inventoryDAO = new InventoryDAO();
        // 新建redis分布式锁类：是对那个库存段进行的加锁
        RLock lock = new RLock("stock_" + goodsSkuId + "_" + stockSegmentSeq);
        // 加分布式锁
        lock.lock();
        // 随机获取一个库存段的商品skuid的库存信息
        Long stock = inventoryDAO.getStock(goodsSkuId, stockSegmentSeq);

        // 如果查出来分段库存是0，那么就应该去找另外一个分段
        // 思路：循环遍历所有的库存段，遍历的时候对于每个库存段进行加锁，直到找到可以满足库存不为0的库存段，停止遍历
        if(stock == 0L) {
            // 释放分布式锁
            lock.unlock();
            // 发现其存段标识
            boolean foundOtherStockSegment = false;
            // 循环每个库存段
            for(int i = 1; i <= 10; i++) {
                // 如果遍历到当前库存为0的库存段，因为之前就发现该库存段的库存为0，则直接执行下一个循环
                if(i == stockSegmentSeq) {
                    continue;
                }
                // 获取分布式锁，并给可以使用的库存段进行加锁
                lock = new RLock("stock_" + goodsSkuId + "_" + i);
                lock.lock();
                // 获取改库存段下的库存信息
                stock = inventoryDAO.getStock(goodsSkuId, i);
                // 当改库存段不为0时，标识已经找到了可以使用的库存段将库存段id和库存段标识张志伟true
                if(stock != 0) {
                    stockSegmentSeq = i;
                    foundOtherStockSegment = true;
                    break;
                } else {
                    lock.unlock();
                }
            }

            // 当循环完真个库存段时候发现都不行，返回库存不足
            if(!foundOtherStockSegment) {
                System.out.println("商品库存不足");
                return;
            }
        }

        // 如果库存分段正好大于要购买的数量
        if(stock >= purchaseCount) {
            inventoryDAO.updateStock(goodsSkuId, stockSegmentSeq, stock - purchaseCount);
            lock.unlock();
            return;
        }

        // 代码走到这里，就证明说，当前这个分段的库存小于要购买的数量，合并分段加锁
        Long totalStock = stock;

        Map<RLock, Long> otherLocks = new HashMap<RLock, Long>();

        // 如果不为0的库存段的库存无法满足购买数量，则开始遍历每个库存段的库存进行相加，直到满足购买数量的时候跳出循环
        for(int i = 1; i <= 10; i++) {
            if(i == stockSegmentSeq) {
                continue;
            }

            RLock otherLock = new RLock("stock_" + goodsSkuId + "_" + i);
            otherLock.lock();

            Long otherStock = inventoryDAO.getStock(goodsSkuId, i);
            if(otherStock == 0) {
                otherLock.unlock();
                continue;
            }

            totalStock += otherStock;
            otherLocks.put(otherLock, otherStock);

            if(totalStock >= purchaseCount) {
                break;
            }
        }

        // 尝试所有的其他分段之后还是无法满足购买数量，释放每个库存段的锁
        if(totalStock < purchaseCount) {
            System.out.println("商品库存不足");
            for(Map.Entry<RLock, Long> otherLockEntry : otherLocks.entrySet()) {
                otherLockEntry.getKey().unlock();
            }
            return;
        }

        // 如果循环每个库存段得到的库存可以满足购买数量，则先将最初加的那个分段库存扣减掉
        Long remainReducingStock = purchaseCount - stock;

        inventoryDAO.updateStock(goodsSkuId, stockSegmentSeq, 0L);
        // 将原库存段的锁进行释放
        lock.unlock();


        for(Map.Entry<RLock, Long> otherLockEntry : otherLocks.entrySet()) {
            if(remainReducingStock == 0) {
                break;
            }
            // 获取每个加锁的库存段的id，并且对于每个库存段的库粗进行相减直到剩余库存被完全减完，同时也修改每个库存段的库存信息
            RLock otherLock = otherLockEntry.getKey();
            int otherStockSegmentSeq = Integer.parseInt(otherLock.name.substring(otherLock.name.length() - 1));
            Long otherStock = otherLockEntry.getValue();

            if(otherStock <= remainReducingStock) {
                remainReducingStock -= otherStock;
                inventoryDAO.updateStock(goodsSkuId, otherStockSegmentSeq, otherStock);
            } else {
                remainReducingStock = 0L;
                inventoryDAO.updateStock(goodsSkuId, otherStockSegmentSeq, otherStock - remainReducingStock);
            }

            otherLock.unlock();
        }
    }

    // 模仿redis锁类
    static class RLock {

        String name;

        public RLock(String name) {
            this.name = name;
        }

        public void lock() {
            System.out.println("加分布式锁：" + name);
        }

        public void unlock() {
            System.out.println("释放分布式锁：" + name);
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((name == null) ? 0 : name.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            RLock other = (RLock) obj;
            if (name == null) {
                if (other.name != null) {
                    return false;
                }
            } else if (!name.equals(other.name)) {
                return false;
            }
            return true;
        }

    }

    // 更新商品库存类
    static class InventoryDAO {
        // 获取商品库存信息
        public Long getStock(Long goodsSkuId, Integer stockSegmentSeq) {
            return 1000L;
        }

        public void updateStock(Long goodsSkuId, Integer stockSegmentSeq, Long stock) {
            System.out.println("更新商品库存，goodsSkuId=" + goodsSkuId
                    + "，stockSegmentSeq=" + stockSegmentSeq
                    + "，stock=" + stock);
        }
    }
}
